package com.wiscamp.ninechapters.threeparties.mathpix;

import com.wiscamp.ninechapters.common.core.DateTimeHelper;
import com.wiscamp.ninechapters.common.exception.ServiceException;
import cube.common.cache.RedisUtils;
import cube.ddd.aspect.HandleServiceException;
import cube.ddd.domain.BaseDomainService;
import jakarta.validation.constraints.NotBlank;
import org.springframework.stereotype.Service;

import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.time.LocalDate;
import java.util.Base64;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * Mathpix OCR Service
 */
@Service
public class MathpixService extends BaseDomainService {
    private static final Recognition recognition = new Recognition();

    private final RedisUtils redisUtils;

    public MathpixService(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }

    private static String encodeImage(byte[] imageByteArray) {
        return Base64.getEncoder().encodeToString(imageByteArray);
    }

    /**
     * Gets latex text from a image URL
     *
     * @param imageUrl
     * @return
     */
    @HandleServiceException()
    public OCRResponse getImageLatex(@NotBlank String imageUrl) {
        if (exceedMathpixApiLimit()) {
            var errorCode = "service-exception.mathpix.api.insufficient";
            throw new ServiceException(null, errorCode, null);
        }

        var executor = Executors.newSingleThreadExecutor();
        var imageBase64 = base64Image(imageUrl);
        if (recognition.setSrcParameters(imageBase64)) {
            Future<OCRResponse> result = executor.submit(recognition);
            try {
                return result.get();
            } catch (InterruptedException iex) {
                logger.error("Broken internet connection. Interrupted exception occurs.", iex);
                Thread.currentThread().interrupt();
                return null;
            } catch (ExecutionException e) {
                logger.error("Broken internet connection. Execution exception occurs.", e);
                return null;
            }
        }
        return null;
    }

    /**
     * Gets latex text from a base64 image String
     *
     * @param base64Image
     * @return
     */
    @HandleServiceException()
    public OCRResponse getLatexFromBase64Image(@NotBlank String base64Image) {
        if (exceedMathpixApiLimit()) {
            var errorCode = "service-exception.mathpix.api.insufficient";
            throw new ServiceException(null, errorCode, null);
        }

        ExecutorService executor = Executors.newSingleThreadExecutor();
        if (recognition.setSrcParameters(base64Image)) {
            Future<OCRResponse> result = executor.submit(recognition);
            try {
                return result.get();
            } catch (InterruptedException iex) {
                logger.error("Broken internet connection. Interrupted exception occurs.", iex);
                Thread.currentThread().interrupt();
                return null;
            } catch (ExecutionException e) {
                logger.error("Broken internet connection. Execution exception occurs.", e);
                return null;
            }
        }
        return null;
    }

    /**
     * 限制每月OCR API调用次数不超过950，避免个人账户信用卡扣费
     *
     * @return
     */
    private boolean exceedMathpixApiLimit() {
        var limit = 950;
        var month = DateTimeHelper.getDateString(LocalDate.now(), "yyyyMM");
        if (redisUtils.hasKey(month)) {
            int count = (int) redisUtils.get(month);
            if (count >= limit) {
                return true;
            } else {
                redisUtils.set(month, count + 1);
                return false;
            }
        } else {
            redisUtils.set(month, 1, 0);
            return false;
        }
    }

    private String base64Image(@NotBlank String imageUrl) {
        try {
            // Reading a Image file from file system
            var url = new URL(imageUrl);
            var stream = new BufferedInputStream(url.openConnection().getInputStream());
            byte[] imageData = new byte[stream.available()];
            stream.read(imageData);
            // Converting Image byte array into Base64 String
            return encodeImage(imageData);
        } catch (FileNotFoundException e) {
            logger.error("Can not find the image file. ", e);
        } catch (IOException ioe) {
            logger.error("Failed to read the image file.", ioe);
        }
        return "";
    }
}
